package ar.com.e2solver.solver.operator.impl;

import java.util.ArrayList;
import java.util.List;

import ar.com.e2solver.exception.BusinessException;
import ar.com.e2solver.model.Coordinate;
import ar.com.e2solver.model.Piece;
import ar.com.e2solver.model.Puzzle;
import ar.com.e2solver.solver.Individual;
import ar.com.e2solver.solver.RandomConstants;
import ar.com.e2solver.solver.operator.MutationOperator;

public class SwapMutationOperator implements MutationOperator {
	
	private static SwapMutationOperator instance = new SwapMutationOperator();
	
	private SwapMutationOperator(){}

	public static SwapMutationOperator getInstance() {
		return instance;
	}

	public Individual operate(Individual individual) throws BusinessException {
		Individual result = null;
		
		// Crea una copia
		Puzzle newPuzzle = new Puzzle(individual.getPuzzle());
		
		int xRandom = RandomConstants.RANDOM.nextInt(newPuzzle.getRowsNumber());
		int yRandom = RandomConstants.RANDOM.nextInt(newPuzzle.getColumnNumbers());
		
		if(	(xRandom == 0 && yRandom == 0) || 
			(xRandom == 0 && yRandom == newPuzzle.getColumnNumbers() -1) || 
			(xRandom == newPuzzle.getRowsNumber() -1 && yRandom == 0) || 
			(xRandom == newPuzzle.getRowsNumber() -1 && yRandom == newPuzzle.getColumnNumbers() -1)){
			
			List<Coordinate> coordinates = getCornerCoordinates(newPuzzle.getRowsNumber(), newPuzzle.getColumnNumbers()); 			
			coordinates.remove(new Coordinate(xRandom, yRandom));
			int randomIndex = RandomConstants.RANDOM.nextInt(coordinates.size());
			Coordinate toSwap = coordinates.get(randomIndex);
			
			// Intercambia las piezas de las esquinas
			if(newPuzzle.canSwap(xRandom, yRandom, toSwap.getX(), toSwap.getY())){
				newPuzzle.swapPositions(xRandom, yRandom, toSwap.getX(), toSwap.getY());
			
				// Acomoda los bordes de las esquinas
				rotateForCorner(xRandom, yRandom, newPuzzle.getPieceAt(xRandom, yRandom));
				rotateForCorner(toSwap.getX(), toSwap.getY(), newPuzzle.getPieceAt(toSwap.getX(), toSwap.getY()));
				
				result = new Individual(newPuzzle);
			}
		} else if(	(xRandom == 0 && yRandom != 0 && yRandom != newPuzzle.getColumnNumbers() -1) ||
					(xRandom == newPuzzle.getRowsNumber() -1 && yRandom != 0 && yRandom != newPuzzle.getColumnNumbers() -1) ||
					(xRandom != 0 && xRandom != newPuzzle.getRowsNumber() -1 && yRandom == 0) ||
					(xRandom != 0 && xRandom != newPuzzle.getRowsNumber() -1 && yRandom == newPuzzle.getColumnNumbers() -1)){
			// Es un borde => Swap entre bordes rotando lo necesario para que coincida
			List<Coordinate> coordinates = getBorderCoordinates(newPuzzle.getRowsNumber(), newPuzzle.getColumnNumbers());
			coordinates.remove(new Coordinate(xRandom, yRandom));
			int randomIndex = RandomConstants.RANDOM.nextInt(coordinates.size());
			Coordinate toSwap = coordinates.get(randomIndex);
			
			// Intercambia las piezas de los bordes
			if(newPuzzle.canSwap(xRandom, yRandom, toSwap.getX(), toSwap.getY())){
				newPuzzle.swapPositions(xRandom, yRandom, toSwap.getX(), toSwap.getY());
			
				// Acomoda los bordes de los bordes
				rotateForBorder(xRandom, yRandom, newPuzzle.getPieceAt(xRandom, yRandom));
				rotateForBorder(toSwap.getX(), toSwap.getY(), newPuzzle.getPieceAt(toSwap.getX(), toSwap.getY()));			
				
				result = new Individual(newPuzzle);
			}
		} else {
			// Es interno = > Swap entre internos
			int x2Random = RandomConstants.RANDOM.nextInt(newPuzzle.getRowsNumber() - 2) + 1;
			int y2Random = RandomConstants.RANDOM.nextInt(newPuzzle.getColumnNumbers() - 2) + 1;
			
			if(newPuzzle.canSwap(xRandom, yRandom, x2Random, y2Random)){
				newPuzzle.swapPositions(xRandom, yRandom, x2Random, y2Random);
				
				result = new Individual(newPuzzle);
			}
		}
		
		if(result == null){
			result = new Individual(new Puzzle(individual.getPuzzle()));
		}
		return result;
	}
	
	private static void rotateForBorder(int i, int j, Piece piece) {
		if(i == 0 ){
			while(!piece.getTop().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}
		} else if(j == 0){
			while(!piece.getLeft().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}
			
		} else if(i > j){
			while(!piece.getBottom().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}
			
		} else {
			while(!piece.getRight().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}	
		}
	}

	private static void rotateForCorner(int i, int j, Piece piece) {
		if(i == 0 && j == 0){
			while(!piece.getLeft().equals(Piece.BORDER_COLOR) || !piece.getTop().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}
		} else if(i == 0 && j != 0){
			while(!piece.getRight().equals(Piece.BORDER_COLOR) || !piece.getTop().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}
		} else if(i != 0 && j == 0){
			while(!piece.getLeft().equals(Piece.BORDER_COLOR) || !piece.getBottom().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}			
		} else {
			while(!piece.getRight().equals(Piece.BORDER_COLOR) || !piece.getBottom().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}			
		}
	}
	
	private static List<Coordinate> getCornerCoordinates(int row, int col) {
		List<Coordinate> coordinates = new ArrayList<Coordinate>();
		coordinates.add(new Coordinate(0,0));
		coordinates.add(new Coordinate(0, col -1));
		coordinates.add(new Coordinate(row -1, 0));
		coordinates.add(new Coordinate(row -1, col -1));
		return coordinates;
	}
	
	private static List<Coordinate> getBorderCoordinates(int row, int col) {
		List<Coordinate> borderCoordinates = new ArrayList<Coordinate>();

		for(int i = 0; i < row; i++){
			borderCoordinates.add(new Coordinate(i, 0));
			borderCoordinates.add(new Coordinate(i, col - 1));
		}
		
		for(int j = 0; j < col; j++){
			borderCoordinates.add(new Coordinate(0, j));
			borderCoordinates.add(new Coordinate(row - 1, j));
		}
		
		// Saca las esquinas
		borderCoordinates.removeAll(getCornerCoordinates(row, col));
		
		return borderCoordinates;
	}	

}
